$MOD186
Name Serial

; Serial routines for 8088 microcomputer system
;
; Filename:	Serial.ASM
;
; Author: 	Law Wen Yong
;
; This file contains proprietory information and cannot be copied 
; or distributed without prior permission from the author.
;---------------------------------------------------------------------------
;API:
public print_char ;transmit char to hyperterminal. Parameter is AL
public print_2hex ;to convert integer type into Ascii characters before transmitting to hyperterminal for display.  Parameter is AL
public Serial_initialise ;initialise serial settings
public SERIAL_INTR ;Serial transmit/receive interrupt routine
public Serial_check_Received_status ;return parameter AL: Serial_Received_status , AH: Type of data to received
public Serial_Get_data ;get data from serial. Parameter DI - offset address you want to copy to, ES - segment address you want to copy to
public Serial_send_protocol ; use to transmit protocol. ;parameter AL-state the type 2 or type 4 for LCD
extrn write_char_to_LCD:far ;testing



Serial_DS SEGMENT
	Board_ID DB 81H 
	Serial_Received_status DB Serial_no_data_in_RBuffer
	
	;Recieved Status:
	Type_of_Data_received DB 0H ;initial no message received
	Serial_Received_State DB Serial_wait_for_poll
	Serial_Unstuff_mode DB 0H
	;Sent Items
	Type_2_protocol DB 5,7EH,81H,02H,'!',7FH ;number in front states the protocol size in bytes
	Type_4_protocol DB 5,7EH,81H,04H,'!',7FH
	
Serial_DS ENDS


Received_buffer SEGMENT
    RBuffer_LEN	EQU	128 ;size of the queue ;about 128 bytes (note that this value is constant)

	RBuffer_TRANS	DB	RBuffer_LEN DUP(?) ;create the array of the queue according to this length
	RBuffer_HEAD	DW	0H ;index of the head in the array
	RBuffer_TAIL	DW	0H ;index of the tail in the array
	RBuffer_cur_size DB 0H

Received_buffer ENDS

RBuffer_BASE	EQU	OFFSET RBuffer_TRANS 

;Queue for transmiting the serial data
;This queue is a circular array/buffer for storing data temporary before 
;transmision
;For the function below, it will do this:
;if there is something in the queue, the transmission interrupt will be 
;enable
;each time when the transmission holding register is empty , a 
;transmission interrupt will occured and
;it will extract from the head of the queue into the transmission holding 
;register
;if everything in the queue has been transmitted, the transmission 
;interrupt will be disabled
INT_RAM_AREA	SEGMENT
	QUEUE_LEN	EQU	128 ;size of the queue ;about 128 bytes (note that this value is constant)

	QUEUE_TRANS	DB	QUEUE_LEN DUP(?) ;create the array of the queue according to this length
	QUEUE_HEAD	DW	0H ;index of the head in the array
	QUEUE_TAIL	DW	0H ;index of the tail in the array
INT_RAM_AREA	ENDS

QUE_BASE	EQU	OFFSET QUEUE_TRANS  ;defined in the orignal code

$include(80188.inc)
$include(Serial.inc)


Serial_Routine SEGMENT
ASSUME CS:Serial_Routine



Serial_initialise PROC FAR
	;push BX
	;push DS
	;push DI
;Settings of the serial port transmission
;1st bit set 1 to access the Divisor latch 
;2 stop bits, 8 data bits, no parity check
        SMD_DATA_DIV EQU    10000111B
 ; set two stop bits, 8 data bits as well set the DLAB to 1 so that you could change the divisor value
        MOV DX, SMD
        MOV AL, SMD_DATA_DIV  
        OUT DX, AL
 ;set the baud rate to 9600bps.  divisor value =8MHz/(9600*16) = 52
        MOV DX, DLL
        MOV AL, 52     
        OUT DX, AL
        MOV DX, DLM
        MOV AL, 0
        OUT DX, AL
		
;SET SERIAL PORT WORKING MODE ;set the DLAB to 1 so that you could access Receiver Buffer, Transmitter holding register and Interrupt Enable Register
         MOV DX, SMD
         MOV AL, SMD_DATA
         OUT DX, AL
; Enable serial interrupts
		MOV	AL,S_INT_ENA
		OUT	SIER,AL
; =============== INITIALIZATION OF INTERRUPT CONTROL UNIT =============
; Initialize ICU for operation
		
; Mask all interrupts except SCU
                ;disable TX interrupt,ENABLE RX.
		MOV	AL,1
		OUT	SIER,AL
		
		;initialised machine id
		;MOV BX, Serial_DS
		;MOV DS, BX
		;MOV BL, 81H
		;MOV DS:Board_ID, BL
		;MOV DI, offset Type_1_protocol
		;INC DI
		;MOV [DI],BL
		;MOV DI, offset Type_3_protocol
		;INC DI
		;MOV [DI],BL
	;pop DI	
	;pop DS
	;pop BX
	ret
Serial_initialise ENDP

;parameter AL-state the type 2 or type 4 for LCD
Serial_send_protocol proc far
	push DS
	push BX
	push SI
	push CX
	Mov BX, Serial_DS
	Mov DS, BX
	
	;check if AL is 2,
	cmp AL, 2
	jne Serial_check_protocol_type_part_1
	;if AL is 2
	;move type 2 to SI
	Mov SI, offset Type_2_protocol
	jmp Serial_protocol_send_now
	
	Serial_check_protocol_type_part_1:
	;check if AL is 4
	cmp AL, 4
	jne Serial_check_protocol_exit ;AL contains invalid type
	;if AL is 4,
	;mov type 4 to SI
	Mov SI, offset Type_4_protocol
	jmp Serial_protocol_send_now
		
	
	Serial_protocol_send_now:
	;Mov size to cx
	Mov CL, [SI]
	MOV CH, 0
	inc SI
	Serial_protocol_sending_loop:
	Mov AL,[SI]
	call far ptr print_char
	
	inc SI
	loop Serial_protocol_sending_loop
	
	
	
	Serial_check_protocol_exit:
	pop CX
	pop SI
	pop BX
	pop DS
	ret
Serial_send_protocol endp



; ----------------Start of procedure PRINT_2HEX ------------------------
PRINT_2HEX 	PROC	FAR 
	
; The byte to be printed as 2 HEX number is put into AL.
; This procedure is then called.
		CALL	FAR PTR CHAR2HEX
; Result is return in AX
		PUSH 	AX  ;the hexadecimal digit in ascii is now stored in AX. push it into the stack now
		MOV	AL,AH   ;move the upper digit to AL
		CALL	FAR PTR PRINT_CHAR  ; transmit the upper digit
		POP	AX                ;now AL contains lower digit
		CALL	FAR PTR PRINT_CHAR ;transmit the lower digit
		RET
PRINT_2HEX	ENDP


;------------------Start of Procedure CHAR2HEX ----------------------------
CHAR2HEX	PROC	FAR 
; Char to be converted to HEX is put in AL before calling this procedure.
; HEX version is return in AX.
		MOV	AH,AL         ;store the full integer into AH
		AND	AL,00001111B  ;mov the lower part of the digit to AL
		CMP	AL,9          
		JG	GT9_1         ;check if more than 9
		OR	AL,00110000B  ;print as digit hexi. zero starts from 30H for ascii
		JMP 	DIGIT2
GT9_1:		SUB	AL,9       ;if more than 9, print as alphabets ascii. 
		OR	AL,01000000B  ;A starts from 41H for ascii
DIGIT2:
		SHR	AH,4          ;shift to right by 4. AH now has the upper digit
		CMP	AH,9          
		JG	GT9_2		 ;check if more than 9
		OR	AH,00110000B  ;print as digit hexi. zero starts from 30H for ascii
		JMP	DONE
GT9_2:		SUB	AH,9
		OR	AH,01000000B  ;A starts from 41H 
DONE:
		RET
CHAR2HEX	ENDP



; ---------------- Start of procedure PRINT_CHAR ------------------------
PRINT_CHAR	PROC	FAR
; This procedure is called to put a character into queue for transmission
; through the serial port. 
; The data to be transmitted is put in AL before the procedure is called.
; Data is put at the tail. Queue_tail is then inc to point to next loc.
; Data is taken from the head. Queue_head is then inc to point to next data.
	
		PUSH	BX			;Save BX	
		PUSH	ES

		PUSH	AX

		MOV	BX,SEG QUEUE_TAIL	;Init segment register.  ;from the original code. another of putting Int_RAM_AREA into ES
		MOV	ES,BX			;ES now contains seg of INT_RAM_AREA

		IN	AL,SIER 		;disable TX interrupt. ;to prevent the queue to be modified by the transmission interrupt when enqueuing is being done
		AND	AL,11111101B    
		OUT	SIER,AL

		POP	AX
		MOV	BX,ES:QUEUE_TAIL	;get the index of the tail
		MOV	ES:QUE_BASE[BX],AL	;Put data to queue_tail.
		INC	ES:QUEUE_TAIL		;Increment queue_tail
		CMP	ES:QUEUE_TAIL,QUEUE_LEN	;and wrap around
		JL	L_PRINT1		;to zero if needed.
		MOV	ES:QUEUE_TAIL,0
L_PRINT1:	
		IN	AL,SIER			;enable TX interrupt after enqueuing to enable transmission to occur
		OR	AL,00000010B
		OUT	SIER,AL

		POP	ES
		POP	BX
		RET
PRINT_CHAR	ENDP



SERIAL_REC_ACTION	PROC	FAR ;AL is the received byte
		PUSH	CX
		PUSH 	BX
		PUSH	DS
 
		
		Mov BX, Serial_DS
		Mov DS,BX
		
		Mov BL, DS:Serial_Received_state
		
		;if polling state,
		cmp BL, Serial_wait_for_poll
		jne Serial_check_for_other_rstate1
		;check if AL is 7E
		cmp AL,7EH
		JNE SR_exit1
		;if AL is 7E ;set state to check ID
		MOV BL, Serial_check_ID_state
		MOV DS:Serial_Received_state, BL
		
		SR_exit1:
		
		JMP SR_exit2
				
		Serial_check_for_other_rstate1:
		;check if it is in serial check ID state
		cmp BL, Serial_check_ID_state
		jne Serial_check_for_other_rstate2
		;if check ID state,
		;check if AL is 7D
		
		cmp AL,7DH
		jne Serial_checking_if_unstuff_needed
		
		;if AL is 7D
		;set to Unstuff mode so that the next byte will be unstuff
		Mov BL, 1H
		mov DS:Serial_Unstuff_mode, BL
		jmp SR_exit2
		
		Serial_checking_if_unstuff_needed:
		;else check if unstuffing needed
		Mov BL, DS:Serial_Unstuff_mode
		cmp BL, 1
		jne Serial_checking_ID
		;if byte unstuffing needed, xor with 20H
		xor AL, 20H
		;disable serial unstuff_mode
		MOV BL,0H
		Mov DS:Serial_Unstuff_mode, BL
		
		
		
		Serial_checking_ID:
		
		;check if this ID is mine
		cmp AL, DS:Board_ID
		jne serial_not_my_ID
		;if this ID is mine
		;set state to Receving state
		Mov BL, Serial_Receiving 
		Mov DS:Serial_Received_State,BL
		jmp SR_exit2
		
		serial_not_my_ID:
		
		;if this ID is not mine:
		;set state to wait for others state
		Mov BL, Serial_wait_for_poll
		Mov DS:Serial_Received_State, BL
		SR_exit2:
		jmp SR_exit3
		
		Serial_check_for_other_rstate2:
		;check if it is in Serial_Receiving state
		cmp BL, Serial_Receiving
		jne SR_exit3
		;if it is in Serial_Receiving state
		;check if AL is 7D
		cmp AL, 7DH
		jne Serial_check_if_finished_receiving
		
		;activate byte stuffing for next byte
		Mov BL, 1
		Mov DS:Serial_Unstuff_mode, BL
		jmp SR_exit3
		
			

		Serial_check_if_finished_receiving:
		cmp AL,7FH
		jne Serial_check_unstuffing2
		;if finished receiving
		;set state to wait for polling
		;set Serial_Received_status to Serial_has_packet_in_RBuffer 
		;change AL to 10 to indicate end of the packet and in the buffer
		;Mov AL, 10
		Mov BL, Serial_has_packet_in_RBuffer 
		Mov DS:Serial_Received_status,BL
						
		Mov BL, Serial_wait_for_poll
		Mov DS:Serial_Received_State,BL
		;JMP Serial_stored_msg_in_buffer
		
		
		

		
		
		SR_exit3:
		JMP SR_exit
		
		
		
		
		Serial_check_unstuffing2:
		;check if byte unstuffing is needed
		Mov BL, DS:Serial_Unstuff_mode
		cmp BL, 1
		jne Serial_stored_msg_in_buffer
		
		;if unstuffing mode is activated,
		xor AL, 20H
		Mov BL, 0H
		Mov DS:Serial_Unstuff_mode, BL
		
				
		Serial_stored_msg_in_buffer:
		MOV BX, Received_buffer
		MOV DS, BX
		MOV BX,DS:RBuffer_TAIL           
		MOV DS:RBuffer_BASE[BX],AL     ;saved the received byte into the buffer
		INC DS:RBuffer_TAIL
		INC DS:RBuffer_cur_Size ;increase cur buffer size
		CMP DS:RBuffer_TAIL, RBuffer_LEN
		JL SR_exit
		MOV DS:RBuffer_TAIL,0

		
		
SR_exit:
		POP	DS
		POP	BX
		POP	CX
		RET
SERIAL_REC_ACTION	ENDP


; ************************************************************************
;			INTERRUPT ROUTINES				 *
; ************************************************************************

; **************** Start of SERIAL_INTR service routine ******************

;*****************CAUTION*****************
;At the end of interrutp routines, you must write EOI (end of Int) +
;with the INT type (INT0-type 12) 		   +	
;comment added by Zhu Shunyu	March,2000			   +				
;Interrupt Routines Modified accordly to fit 80C188XL
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
SERIAL_INTR     proc Far
		PUSH	AX			;save registers
		PUSH	BX
                PUSH    DX

       

               	               
		IN	AL,IIR			;read in serial INT ID
        AND     AL,00000111B
		CMP     AL,00000100B		;check if rx interrupt
		JE	RECEIVE_INTR

		CMP    	AL,00000010B		;check if tx interrupt
		JE	TRANSMIT_INTR

		
;RESET_INT_CTL  ;If it is not any of the transmision/receive interrupt 
;false serial interrupt:
;*****************************************************************
                MOV DX, EOI ;end of interrupt needed for all end of the interrupt routine
                MOV AX, 12 ;send out type number 12
                OUT DX, AL

                POP     DX
		POP	BX			
		POP	AX
		IRET				;return
;**********************************************************************

RECEIVE_INTR:	
              
		IN	AL,SRB		;read in from receive buffer	
; Information received will be used by user routine
; Action to be taken will be contained in SERIAL_REC_ACTION
		CALL	FAR PTR SERIAL_REC_ACTION
		
		MOV DX, EOI ;end of interrupt needed for all end of the interrupt routine
                MOV AX, 12 ;send out type number 12
                OUT DX, AL
		POP     DX
		POP	BX			
		POP	AX
		IRET

TRANSMIT_INTR:
               
		PUSH	ES			;save ES
		MOV	BX,SEG QUEUE_TAIL	;set ES to SERIAL_Q_SEG  ;from the original code. another of putting Int_RAM_AREA into ES
		MOV	ES,BX
		MOV	BX,ES:QUEUE_TAIL
		CMP	BX,ES:QUEUE_HEAD	;more data to be transmitted? 
		JE	L_TX2				;check if the tail index is same as head index
		MOV	BX,ES:QUEUE_HEAD	;get data from head of the queue
		MOV	AL,ES:QUE_BASE[BX]   
		OUT	STB,AL			;tx data by moving the data into STB
		INC	ES:QUEUE_HEAD		;point to next data ;increment the head because the element has been extracted out and put in STB
		CMP	ES:QUEUE_HEAD,QUEUE_LEN ;wrap around if necessary
		JL	L_TX1            
		MOV	ES:QUEUE_HEAD,0
L_TX1:		
		MOV	BX,ES:QUEUE_TAIL   
		CMP	BX,ES:QUEUE_HEAD	;more data to be transmitted?
		JNE	L_TX3
L_TX2:
		IN	AL,SIER			;no more, disable TX interrupt.
		AND    	AL,11111101B
		OUT	SIER,AL
L_TX3:
		
;RESET_INT_CTL
                MOV DX, EOI  ;end of interrupt needed for all end of the interrupt routine
                MOV AX, 12 ;send out type number 12
                OUT DX, AL
      		POP	ES			;restore original ES(transmit)
		
                POP     DX 
                POP	BX			
		POP	AX
		IRET
; **************** End of SERIAL_INTR service routine ************************

SERIAL_INTR ENDP

;return parameter AL: Serial_Received_status , AH: Type of data to received
Serial_check_Received_status proc Far
	push DS
	push BX
	Mov BX, Serial_DS
	Mov DS, BX
	Mov AL, DS:Serial_Received_status 
	Mov Bx, Received_buffer
	Mov DS, BX
	Mov BX,DS:RBuffer_HEAD
	Mov AH, DS:RBuffer_BASE[BX] ;type of data
	pop BX
	pop DS
	ret
Serial_check_Received_status endp


Serial_Get_data proc Far ;get data from serial. Parameter DI - offset address you want to copy to, ES - segment address you want to copy to
  push DS
  push BX
  push AX
  
  MOV BX, Received_buffer
  MOV DS, BX
  
Serial_RBuffer_loop: 
  MOV BX, DS:RBuffer_TAIL
  CMP BX, DS:RBuffer_HEAD
  JE serial_end2
  
  MOV BX,DS:RBuffer_HEAD
  MOV AL,DS:RBuffer_BASE[BX]
  INC DS:RBuffer_HEAD
  DEC DS:RBuffer_cur_size
  
   

  
 ; cmp AL, 10
 ; JE serial_end2
  
  MOV ES:[DI], AL
  INC DI
 ; call far ptr print_2Hex
  
  CMP DS:RBuffer_HEAD, RBuffer_LEN
  
  JL dont_reset_head_to_zero
  MOV DS:RBuffer_HEAD,0
  
 dont_reset_head_to_zero:
  JMP Serial_RBuffer_loop
  
 serial_end2:

	MOV BX, Serial_DS 
	MOV DS, BX
	MOV AL, Serial_no_data_in_RBuffer
	MOV DS:Serial_Received_status ,AL
  
 
  pop AX
  pop BX
  pop DS
  ret
Serial_Get_data ENDP


Serial_ROUTINE	ENDS

End
